/* **********************************************************************************
*
* Copyright (c) Microsoft Corporation. All rights reserved.
*
* This source code is subject to terms and conditions of the Shared Source License
* for DSL Editor PowerToy. A copy of the license can be found in the License.htm file
* at the root of this distribution. If you can not locate the Shared Source License
* for DSL Editor PowerToy, please obtain a copy from: http://www.codeplex.com/dsltreegrideditor/Project/License.aspx.
* By using this source code in any fashion, you are agreeing to be bound by
* the terms of the Shared Source License for DSL Editor PowerToy.
*
* You must not remove this notice, or any other, from this software.
*
* **********************************************************************************/
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.Design;
using System.Reflection;
using System.Text;
using System.Windows.Forms.Design;
using System.Windows.Forms;

using Microsoft.Practices.RecipeFramework.Library;
using Microsoft.Practices.RecipeFramework.Extensions.Editors.ClassBrowser;
using Microsoft.VisualStudio.Shell.Design;
using Microsoft.VisualStudio.Shell.Interop;

using EnvDTE;
using VsWebSite;

namespace DslEditorPowerToy.ComponentModel.Design
{
    public abstract class ClassBrowserEditor : Microsoft.Practices.RecipeFramework.Extensions.Editors.ClassBrowser.ClassBrowserEditor
    {
        private PropertyDescriptor _currentDescriptor = null;
        private List<string> _excludedAssemblies = null;

        #region Private Classes
        /// <summary>
        /// Class to aggregates Assemblies and types
        /// </summary>
        private class TypesAndAssemblies
        {
            private List<Assembly> _assemblies = null;
            private List<Type> _types = null;

            #region Constructors
            public TypesAndAssemblies()
            {
                this._assemblies = new List<Assembly>();
                this._types = new List<Type>();
            }
            #endregion

            #region Public Properties
            public List<Assembly> Assemblies
            {
                get
                {
                    return this._assemblies;
                }
            }
            public List<Type> Types
            {
                get
                {
                    return this._types;
                }
            }
            #endregion
        }
        #endregion

        #region Constructors
        public ClassBrowserEditor()
            : base()
        {
            this._excludedAssemblies = new List<string>();
        }
        #endregion

        #region Public Methods
        public override object EditValue(ITypeDescriptorContext context, IServiceProvider provider, object value)
        {
            //Save current descriptor
            this._currentDescriptor = context.PropertyDescriptor;

            //Get the list of excluded assemblies
            foreach (Attribute attribute in this._currentDescriptor.Attributes)
            {
                if (attribute is ClassBrowserAssemblyExclusionAttribute)
                {
                    ClassBrowserAssemblyExclusionAttribute exclusionAttribute = attribute as ClassBrowserAssemblyExclusionAttribute;
                    this._excludedAssemblies = exclusionAttribute.GetAssemblyNames();
                    break;
                }
            }

            //Get list of valid assemblies and types
            TypesAndAssemblies typesAndAssemblies = GetTypesAndAssemblies(provider);
            if (null == typesAndAssemblies)
                return value;

            //Show dialog
            string typeName = ShowDialog(context, provider, typesAndAssemblies);
            if (false == string.IsNullOrEmpty(typeName))
                return typeName;
            else
                return value;
        }
        #endregion

        #region Protected Properties
        /// <summary>
        /// Returns list of excluded assemblies
        /// </summary>
        protected List<string> ExcludedAssemblies
        {
            get
            {
                return this._excludedAssemblies;
            }
        }
        #endregion

        #region Protected Methods
        /// <summary>
        /// Returns whether to include the assembly
        /// </summary>
        /// <param name="assembly"></param>
        /// <returns></returns>
        protected virtual bool ShouldInclude(Assembly assembly)
        {
            return !IsExcludedAssembly(assembly);
        }
        #endregion

        #region Private Methods
        /// <summary>
        /// Returns whether assembly has been explicity excluded using attributes on property
        /// </summary>
        /// <returns></returns>
        private bool IsExcludedAssembly(Assembly assembly)
        {
            //Check if assembly is excluded
            return this._excludedAssemblies.Contains(assembly.GetName().Name);
        }
        /// <summary>
        /// Returns a collection of assemblies to use to initialise the browser form
        /// </summary>
        /// <param name="context"></param>
        /// <param name="provider"></param>
        private TypesAndAssemblies GetTypesAndAssemblies(IServiceProvider provider)
        {
            //Get the current project
            IVsHierarchy hier = DteHelper.GetCurrentSelection(provider);
            
            //Get the type discovery for the project's scope
            DynamicTypeService typeService = (DynamicTypeService)provider.GetService(typeof(DynamicTypeService));
            ITypeDiscoveryService typeDiscovery = typeService.GetTypeDiscoveryService(hier);

            //Check if web project (special case)
            Project project = ToDteProject(hier);
            if (DteHelper.IsWebProject(project))
            {
                //Get assemblies from web project
                VSWebSite vsProject = (VSWebSite)project.Object;
                List<string> assemblies = new List<string>();
                foreach (AssemblyReference reference in vsProject.References)
                {
                    if (!string.IsNullOrEmpty(reference.FullPath))
                        assemblies.Add(reference.FullPath);
                }

                //Set private data (HACK)
                MethodInfo setAsssembliesMethod = typeDiscovery.GetType().GetMethod(SetAssembliesMethodName,
                    BindingFlags.NonPublic | BindingFlags.Instance);
                setAsssembliesMethod.Invoke(typeDiscovery, new object[] { assemblies.ToArray() });
            }

            if (null == typeDiscovery)
                return null;

            //Find assemblies and types in current project scope
            TypesAndAssemblies typesAndAssemblies = new TypesAndAssemblies();
            List<string> assembliesIncluded = new List<string>();
            List<string> assembliesExcluded = new List<string>();
            foreach (Type type in typeDiscovery.GetTypes(typeof(object), false))
            {
                //Get type's assembly
                Assembly assembly = type.Assembly;

                //Ensure asembly not already excluded (string array is quicker search)
                if (false == assembliesExcluded.Contains(assembly.FullName))
                {
                    //Check if a valid type
                    if (true == ShouldInclude(type))
                    {
                        //Ensure assembly not already included (string array is quicker search)
                        if (false == assembliesIncluded.Contains(assembly.FullName))
                        {
                            //Check if valid assembly
                            if (true == ShouldInclude(assembly))
                            {
                                //Add assembly
                                assembliesIncluded.Add(assembly.FullName);
                                typesAndAssemblies.Assemblies.Add(assembly);

                                //Add type
                                typesAndAssemblies.Types.Add(type);
                            }
                            else
                            {
                                assembliesExcluded.Add(assembly.FullName);
                            }
                        }
                        else
                        {
                            //Add type
                            typesAndAssemblies.Types.Add(type);
                        }
                    }
                }
            }

            return typesAndAssemblies;
        }
        /// <summary>
        /// Displays the editor dialog
        /// </summary>
        /// <returns></returns>
        private string ShowDialog(ITypeDescriptorContext context, IServiceProvider provider,
            TypesAndAssemblies typesAndAssemblies)
        {
            ClassBrowserEditorForm editorForm = new ClassBrowserEditorForm(typesAndAssemblies.Assemblies, typesAndAssemblies.Types);

            //Show the form
            DialogResult result = DialogResult.Cancel;
            IWindowsFormsEditorService formsService = (IWindowsFormsEditorService)provider.GetService(typeof(IWindowsFormsEditorService));
            if (formsService != null)
                result = formsService.ShowDialog(editorForm);
            else
                result = editorForm.ShowDialog();

            //Check result
            if (DialogResult.OK == result)
                return editorForm.TypeFullName;
            else
                return null;
        }
        #endregion
    }
}
